#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <mutex>
#include "util.hpp"
#include "log.hpp"
// 用于倒排文档的权重
#define X 10
#define Y 1
namespace ns_index
{
  // 正排索引的文档描述结构体
  struct DocInfo
  {
    std::string title;
    std::string content;
    std::string url;
    // 文档id
    std::uint64_t doc_id;
  };
  // 倒排索引的文档描述结构体
  struct InvertedElem
  {
    // 文档ID
    std::uint64_t doc_id;
    // 关键字
    std::string word;
    // 权重
    int weight;
  };
  // 通过关键字找到的一个或多个文档(用vector存储起来)  叫做倒排拉链
  typedef std::vector<InvertedElem> InvertedList;

  class Index
  {
    // 把index构建成单例模式
  private:
    Index() {}
    Index(const Index &) = delete;
    Index &operator=(const Index &) = delete;
    static Index *indence;
    static std::mutex mtx;
  public:
    ~Index()
    {
    }
  public:
    static Index *GetIndex()
    {
      if (nullptr == indence)
      {
        mtx.lock();
        if (nullptr == indence)
        {
          indence = new Index();
        }
        mtx.unlock();      
      }
      return indence;
    }

 
    // 通过文档id找到文档的内容 --正排索引
    DocInfo *GetForwardIndex(std::uint64_t doc_id)
    {

      if (doc_id >= forward_index.size())
      {

        std::cerr << "输入id不合法,越界了" << std::endl;
        return nullptr;
      }
      return &forward_index[doc_id];
    }
    // 通过关键字获得倒排拉链
    InvertedList *GetInverted(const std::string &word)
    {
      auto iter = inverted_index.find(word);
      if (iter == inverted_index.end())
      {
        std::cerr << "该关键字找不到对应文档" << std::endl;
        return nullptr;
      }
      return &(iter->second);
    }

    // 通过parser处理过-去标签及格式化后的文件内容来建立正排和倒排索引
    bool BuildIndex(const std::string &input_path)
    {
      std::ifstream in(input_path, std::ios::in | std::ios::binary);
      if (!in.is_open())
      {
        std::cerr << "inputfile_path error" << std::endl;
        return false;
      }
      std::string line;
      int count = 0;
      // 通过getline的方式对文件进行读取
      while (getline(in, line))
      {

        // 建立正排索引
        DocInfo *doc = BuildForwardIndex(line);
        if (nullptr == doc)
        {
          std::cerr << "build" << line << "fail" << std::endl; // for debug
          continue;
        }
        // 建立倒排索引

        BuildInvertedIndex(*doc);
        count++;
        if (count % 100 == 0)
        {
          LOG(NORMAL,"当前已经建立的索引文档: "+std::to_string(count));
          //std::cout << "目前已建立的索引文档: " << count << std::endl;
        }
      }
      in.close();
      return true;
    }

  private:
    // 建立正排索引
    DocInfo *BuildForwardIndex(std::string &line)
    {
      // 1.解析line进行字符串切割
      std::vector<std::string> results;
      std::string sep = "\3";
      // line -> 3 string, title, content, url
      ns_util::StringUtil::CutString(line, results, sep);
      // 2.将切割好的字符串填充到DocInfo
      if (results.size() != 3)
      {
        std::cerr << "Cut fail" << std::endl;
        return nullptr;
      }
      DocInfo doc;
      doc.title = results[0];
      doc.content = results[1];
      doc.url = results[2];
      // 3.插入到正排索引的vector // 先进⾏保存id，在插⼊，对应的id就是当前doc在vector中的下标!
      doc.doc_id = forward_index.size();       // doc_id = 0
      forward_index.push_back(std::move(doc)); // forward_index.size()++ =1
      // 获取最后一个元素--最新的doc交给倒排函数构建倒排索引
      return &forward_index.back();
    }
    // 建立倒排索引
    bool BuildInvertedIndex(const DocInfo &doc)
    {
      // 先对doc.title doc.content中的内容进行分词 吃葡萄
      std::vector<std::string> title_words;
      ns_util::JiebaUtil::CutString(doc.title, &title_words);
      // 用于统计title和content的词频
      struct word_cnt
      {
        int title_cnt;
        int content_cnt;
        word_cnt() : title_cnt(0), content_cnt(0) {}
      };
      std::unordered_map<std::string, word_cnt> word_map;
      // title_words -- 吃 葡萄
      for (std::string &e : title_words)
      {
        boost::to_lower(e);
        word_map[e].title_cnt++;
      }
      std::vector<std::string> content_words;
      ns_util::JiebaUtil::CutString(doc.content, &content_words);
      for (std::string &e : content_words)
      {
        boost::to_lower(e); // 把分词转化为小写 倒排索引不区分大小写
        word_map[e].content_cnt++;
      }
      // 由分词映射倒排拉链
      for (auto &word_pair : word_map)
      {
        InvertedElem elm;
        elm.doc_id = doc.doc_id;
        elm.word = word_pair.first;
        elm.weight = X * word_pair.second.title_cnt + Y * word_pair.second.content_cnt;
        InvertedList &in_list = inverted_index[word_pair.first]; // 根据关键字生成对应的倒排拉链 若没有则新建 若存在使用旧的
        in_list.push_back(elm);
      }

      return true;
    }

  private:
    // 正排索引是用文档id来对应文档  比如 1-文档1
    // 是一对一的关系 所以用数组来对正排索引进行管理
    std::vector<DocInfo> forward_index;
    // 倒排索引是用关键字来对应文档  比如 王某韬-文档1,文档2
    // 可能是一对多的关系 所以用unordered_map来对倒排索引进行管理
    std::unordered_map<std::string, InvertedList> inverted_index;
  };
  Index *Index::indence = nullptr;
  std::mutex Index::mtx;
}